/**
    woofs.c - The main woofs program.  Contains all FS interface functions and
    directory tree builders.
    Copyright (C) 2009  Abram Magner

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>

#define FUSE_USE_VERSION 26	// Required for use of the new API.

#include <fuse.h>

#include "common.h"
#include "list.h"
#include "dirtree.h"
#include "tags.h"
#include "plugin.h"
#include "woofs_plugin.h"
#include "strfuncs.h"
#include "path.h"

#define COND_FREE(ptr) {if (ptr != NULL) free (ptr);}

char usage[] = "Usage: woofs <source directory> <mount directory> <plugin directory> <filters>\n";
FILE *logfile = NULL;

// The tree on which all FS functions operate.
fs_tree *dirtree = NULL;
// The set of plugins for harvesting tags.
plugin_set *woofs_plugin_set = NULL;


//====================================Function prototypes.=====================
fs_tree *woofs_construct_tree (char *, list *);
int make_tree_aux (char *, list *, tree_node *);
char *get_woofs_path (char *, list *, plugin_set *);

static int woofs_getattr (const char *, struct stat *);
static int woofs_readdir (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *);
static int woofs_mknod (const char *, mode_t, dev_t);
static int woofs_mkdir (const char *, mode_t);
static int woofs_unlink (const char *);
static int woofs_rmdir (const char *);
static int woofs_symlink (const char *, const char *);
static int woofs_rename (const char *, const char *);
static int woofs_link (const char *, const char *);
static int woofs_chmod (const char *, mode_t);
static int woofs_chown (const char *, uid_t, gid_t);
static int woofs_truncate (const char *, off_t);
static int woofs_utime (const char *, struct utimbuf *);
static int woofs_open (const char *, struct fuse_file_info *);
static int woofs_read (const char *, char *, size_t, off_t, struct fuse_file_info *);
static int woofs_write (const char *, const char *, size_t, off_t, struct fuse_file_info *);
static int woofs_statfs (const char *, struct statvfs *);
static void woofs_destroy (void *);

static struct fuse_operations fops =
  {
//    .init = woofs_init,
    .getattr = woofs_getattr,
    .mknod = woofs_mknod,
    .mkdir = woofs_mkdir,
    .unlink = woofs_unlink,
    .rmdir = woofs_rmdir,
    .symlink = woofs_symlink,
    .rename = woofs_rename,
    .link = woofs_link,
    .chmod = woofs_chmod,
    .chown = woofs_chown,
    .truncate = woofs_truncate,
    .utime = woofs_utime,
    .open = woofs_open,
    .read = woofs_read,
    .write = woofs_write,
    .statfs = woofs_statfs,
    .readdir = woofs_readdir,
    .destroy = woofs_destroy
  };
/*
    getattr
    readlink
    getdir
    mknod
    mkdir
    unlink
    rmdir
    symlink
    rename
    link
    chmod
    chown
    truncate
    utime
    open
    read
    write
    statfs
    flush
    release
    fsync
    setxattr
    getxattr
    listxattr
    removexattr
  };
*/

//=============================================================================
// Initialize the file system.
//=============================================================================
/**
static void *woofs_init (struct fuse_conn_info *conn)
{
  // TODO: Implement me!
  return (NULL);
}
*/

//=============================================================================
//  Fill the stat buffer with useful information about the path.
//=============================================================================
static int woofs_getattr (const char *path, struct stat *buf)
{
  int result = 0;
  int retval = 0;
  tree_node *pathnode = NULL;
  char *relpath = NULL;
  if ((relpath = path_getrelative (path, "/")) == NULL) return (-errno);
  
  if ((pathnode = tree_lookup (dirtree, relpath)) == NULL)
    {
      // TODO: Set errno appropriately.
      retval = -errno;
      goto woofs_getattr_end;
    }
  // Use stat if the path is real.
  if (pathnode->flags & REAL_FLAG)
    {
      char *actual_path = NULL;
      // If it's a directory, no need to translate, since a real directory
      // can't be the child of a fake one.
      if (pathnode->flags & DIR_FLAG)
	{
	  actual_path = strconcat (source_path, "/");
	  actual_path = mstrconcat (actual_path, relpath);
	}
      // If it's a file, we must translate.
      else
	{
	  actual_path = tree_get_real_path (pathnode, source_path);
	}
      if (actual_path == NULL)
	{
	  // TODO: Set errno appropriately.
	  retval = -errno;
	  goto woofs_getattr_end;
	}
      result = lstat (actual_path, buf);
      free (actual_path);
      if (result < 0) retval = -errno;

    }
  // Otherwise, we need to fill buf with our own stuff.
  else
    {
      // Pathnode should be a directory, since, by convention, all files in the
      // mounted directory have real counterparts and are, thus, marked as real.
      if (pathnode->flags & DIR_FLAG)
        {
          buf->st_mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR;
        }  
      else buf->st_mode = S_IFREG;
      buf->st_mtime = pathnode->mod_timestamp;
    }

woofs_getattr_end:
  free (relpath);
  return (retval);
}


/**
static int woofs_readlink (const char *, char *, size_t)
{
}
*/

//=============================================================================
// Populate the directory with files and subdirectories.  See fuse/fuse.h for
// details.
//
// The filler argument is the function used to do the populating.
//=============================================================================
static int woofs_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
  int retval = 0;
  tree_node *dirnode = NULL;
  list_node *child_iter = NULL;
  char *relpath = NULL;
  if ((relpath = path_getrelative (path, "/")) == NULL) return (-errno);

  if ((dirnode = tree_lookup (dirtree, relpath)) == NULL)
    {
      // TODO: Set errno appropriately.
      retval = -errno;
      goto woofs_readdir_end;
    }
  
  // Add the names of all of dirnode's children.
  child_iter = list_get_iter (dirnode->children);
  while (list_iter_hasmore (child_iter))
    {
      struct stat statbuf;
      tree_node *curchild = child_iter->data;
      // TODO: Make this more efficient by using a function that doesn't monkey
      // with the path (which this function has already done).
      woofs_getattr (path, &statbuf);

      if (filler (buf, curchild->name, &statbuf, 0) != 0)
	{
	  retval = -errno;
	  goto woofs_readdir_end;
	}
      list_iter_advance (child_iter);
    }

woofs_readdir_end:
  free (relpath);
  return (retval);
}

//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_mknod (const char *path, mode_t mode, dev_t device)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_mkdir (const char *path, mode_t mode)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_unlink (const char *path)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_rmdir (const char *path)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_symlink (const char *from, const char *to)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_rename (const char *from, const char *to)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_link (const char *from, const char *to)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_chmod (const char *path, mode_t mode)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_chown (const char *path, uid_t uid, gid_t gid)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_truncate (const char *path, off_t size)
{
  return (0);
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_utime (const char *path, struct utimbuf *times)
{
  return (0);
}



//=============================================================================
// Perform an open operation on the given virtual path.  This consists of 
// converting the virtual path to a real path and opening the real file.
//
// @param path A path to the file in the virtual directory, taking the mount
//   directory as the root.
// @param fi
// @return
//
// TODO: Schedule the file for rescanning if it gets changed.
//=============================================================================
static int woofs_open (const char *path, struct fuse_file_info *fi)
{
  int retval = 0;
  int newfd = 0;
  char *real_path = NULL;
  tree_node *dirnode = NULL;
  char *relpath = NULL;

  LOG(logfile, path);
  // Trim the initial slash from the path.
  if ((relpath = path_getrelative (path, "/")) == NULL)
    {
      // TODO: Set errno properly.
      retval = -errno;
      goto woofs_open_end;
    }
  LOG(logfile, relpath);
  dirnode = tree_lookup (dirtree, relpath);
  if (dirnode == NULL)
    {
      retval = -errno;
      goto woofs_open_end;
    }
  real_path = tree_get_real_path (dirnode, source_path);
  if (real_path == NULL)
    {
      retval = -errno;
      goto woofs_open_end;
    }
  newfd = open (real_path, fi->flags);
  if (newfd < 0)
    {
      retval = -errno;
      goto woofs_open_end;
    }
  fi->fh = (uint64_t)newfd;
  free (real_path);

woofs_open_end:
  free (relpath);
  return (retval);
}


//=============================================================================
//=============================================================================
static int woofs_read (const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
  lseek (fi->fh, offset, SEEK_SET);
  return (read (fi->fh, buf, size));
}


//=============================================================================
// Write the given bytes to the given location in the file.
//=============================================================================
static int woofs_write (const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{

  lseek (fi->fh, offset, SEEK_SET);
  return (write (fi->fh, buf, size));
}


//=============================================================================
// TODO: Implement me!
//=============================================================================
static int woofs_statfs (const char *path, struct statvfs *stbuf)
{
  return (0);
}


static void woofs_destroy (void *ptr)
{
  free_tree (dirtree);
  plugin_set_fini (woofs_plugin_set);
}

//================================= Initialization functions! =================

//=============================================================================
// Construct the woofs file tree corresponding to the given directory.
//
// For each tagged file encountered, 
//=============================================================================
fs_tree *woofs_construct_tree (char *dirpath, list *filters)
{
  fs_tree *tree = NULL;

  if ((tree = new_tree ()) == NULL) return (NULL);
  
  // Initialize the root.
  tree->root = new_tree_node ("", REAL_FLAG | DIR_FLAG, NULL);
  if (tree->root == NULL)
    {
      free_tree (tree);
      return (NULL);
    }
  // Recurse on the current directory.
  if (make_tree_aux (dirpath, filters, tree->root) < 0)
    {
      free_tree (tree);
      return (NULL);
    }
  return (tree);
}

//=============================================================================
// A recursive function for constructing the FS tree.  Given the path to a
// real directory, it constructs the FS subtree rooted at parent.
//
// Base case: There are no real directories in the given parent dir.
//=============================================================================
int make_tree_aux (char *realpath, list *filters, tree_node *parent)
{
  DIR *dhandle = NULL;
  struct dirent *entry_iter = NULL;

  dhandle = opendir (realpath);
  if (dhandle == NULL) return (-1);

  while ((entry_iter = readdir (dhandle)) != NULL)
    {
      char *entry_path = NULL;
      if (strcmp (entry_iter->d_name, ".") == 0 || strcmp (entry_iter->d_name, "..") == 0)
	continue;
      entry_path = strconcat (realpath, "/");
      entry_path = mstrconcat (entry_path, entry_iter->d_name);
      if (entry_path == NULL)
	{
	  closedir (dhandle);
	  return (-1);
	}
      // If it's a directory, create a child for parent, then recurse on it.
      if (path_isdir (entry_path))
	{
	  tree_node *child = tree_addchild (parent, entry_iter->d_name, DIR_FLAG | REAL_FLAG);
	  if (child == NULL)
	    {
	      free (entry_path);
              closedir (dhandle);
	      return (-1);
	    }
	  make_tree_aux (entry_path, filters, child);
	}

      // If it's a regular file, get its woofs path and insert it.
      // This is the base case.
      else if (path_isreg (entry_path))
	{
	  char *woofs_path = get_woofs_path (entry_path, filters, woofs_plugin_set);
	  if (woofs_path == NULL)
	    {
	      free (entry_path);
	      closedir (dhandle);
	      return (-1);
	    }
//	  printf ("woofs_path is \"%s\"\n", woofs_path);
	  tree_insertpath (parent, woofs_path, REAL_FLAG);

          free (woofs_path);  
	}
      free (entry_path);
    }
  closedir (dhandle);
  return (0);
}

//=============================================================================
// Translate the absolute real path of the given file to the corresponding
// canonicalized woofs path, based on the given filters.
//
// Arguments:
//   real_file_path: 
//   filters:
//   pset:
//=============================================================================
char *get_woofs_path (char *real_file_path, list *filters, plugin_set *pset)
{
  // The tags from the given file.
  tag_set *tags = NULL;
  // The path to the file relative to the mount path.
  char *relative_file_path = NULL;
  char *filename = NULL;
  // For traversing the filter list.
  list_node *filter_iter = NULL;

  // Get the file path relative to the mounted directory.
  if ((relative_file_path = path_getrelative (real_file_path, source_path)) == NULL)
    return (NULL);
  if ((filename = path_head (relative_file_path)) == NULL)
    {
      free (relative_file_path);
      return (NULL);
    }
  relative_file_path = path_mtail (relative_file_path);
  if (relative_file_path == NULL)
    {
      free (filename);
      return (NULL);
    }
  if ((tags = extract_tags (real_file_path, pset)) == NULL)
    {
      // We couldn't get the tags, so just place this one in the root.
      free (relative_file_path);
      return (filename);
    }

  // Iterate over the filters, adding a directory per filter.
  filter_iter = list_get_iter (filters);
  while (list_iter_hasmore (filter_iter))
    {
      char *curfilt = filter_iter->data;
      char *tagval = tag_set_lookup (tags, curfilt);

      // If this file doesn't contain the given tag, stop here.
      if (tagval == NULL) break;

      // relative_file_path += relative_file_path + "/" + tagval.
      if (strcmp (relative_file_path, "")) 
	relative_file_path = mstrconcat (relative_file_path, "/");
      relative_file_path = mstrconcat (relative_file_path, tagval);
      free (tagval);
      if (relative_file_path == NULL)
	{
	  tag_set_free (tags);
	  return (NULL);
	}
      list_iter_advance (filter_iter);
    }

  tag_set_free (tags);
  if (relative_file_path != NULL && strcmp (relative_file_path, "") && relative_file_path[strlen (relative_file_path) - 1] != '/')
    relative_file_path = mstrconcat (relative_file_path, "/");
  relative_file_path = mstrconcat (relative_file_path, filename);
  free (filename);
  return (relative_file_path);
}



int main (int argc, char **argv)
{
  struct fuse_args args = FUSE_ARGS_INIT (0, NULL);
  char *plugin_dir = NULL;
  list *filters = NULL;
  int index = 0;
  if (argc < 4)
    {
      fprintf (stderr, usage);
      return (-1);
    }
  if ((logfile = fopen ("woofs-log.txt", "w")) == NULL)
    {
      fprintf (stderr, "Error: Unable to create the log file.\n");
      return (-1);
    }
 
  // Mount the source directory and construct the virtual file system tree.
  // At the end of this stuff, mount_path, source_path, dirtree, and
  // woofs_plugin_set should all be set.
  source_path = path_full (argv[1]);
  mount_path = path_full (argv[2]);
  plugin_dir = argv[3];
  woofs_plugin_set = plugin_set_new ();
  if (source_path == NULL || mount_path == NULL || woofs_plugin_set == NULL)
    {
      COND_FREE (source_path);
      COND_FREE (mount_path);
      if (woofs_plugin_set != NULL) plugin_set_fini (woofs_plugin_set);
      fprintf (stderr, "woofs: Ran out of memory.\n");
      return (-1);
    }
  plugin_scandir (woofs_plugin_set, plugin_dir);
  if (woofs_plugin_set->length == 0)
    {
      free (source_path);
      free (mount_path);
      plugin_set_fini (woofs_plugin_set);
      fprintf (stderr, "woofs: No plugins found!\n");
      return (-1);
    }
  if ((filters = new_list ()) == NULL)
    {
      free (source_path);
      free (mount_path);
      plugin_set_fini (woofs_plugin_set);
      fprintf (stderr, "woofs: Ran out of memory.\n");
      return (-1);
    }
  for (index = 4; index < argc; index++)
    {
      char *curfilt = strdup (argv[index]);
      if (curfilt == NULL)
	{
	  free (source_path);
	  free (mount_path);
	  plugin_set_fini (woofs_plugin_set);
	  free_list_and_data (filters);
	  fprintf (stderr, "woofs: Ran out of memory.\n");
	  return (-1);
	}
      list_insert (filters, filters->length, curfilt);
    }
  dirtree = woofs_construct_tree (source_path, filters);
  free_list_and_data (filters);
  if (dirtree == NULL)
    {
      free (source_path);
      free (mount_path);
      plugin_set_fini (woofs_plugin_set);
      fprintf (stderr, "woofs: Tree construction failed.\n");
      return (-1);
    }

  // The program name and the mount directory are required by fuse.
  fuse_opt_add_arg (&args, argv[0]);
  fuse_opt_add_arg (&args, argv[2]);
  return (fuse_main (args.argc, args.argv, &fops, NULL));
}
